﻿/* 
* Copyright (c) 2010, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Windows;
using System.Windows.Media;

namespace Bio.Views.Structure.Editor.Internal.Rendering.Primitives
{
    public class BasePairVector
    {
        public BasePairVector()
        {
            FivePrimeNucleotideLocation = new Point(0, 0);
            ThreePrimeNucleotideLocation = new Point(0, 0);
            Center = new Point(0, 0);
            RayAngle = 0.0;
            Distance = 0.0;
        }

        public BasePairVector(Point fivePrimeNucleotideLocation, 
            Point threePrimeNucleotideLocation)
        {
            FivePrimeNucleotideLocation = fivePrimeNucleotideLocation;
            ThreePrimeNucleotideLocation = threePrimeNucleotideLocation;
            UpdateVector();
        }

        /// <summary>
        /// Effectively a 2-D translation movement of the vector from its effective center
        /// </summary>
        /// <param name="delta"></param>
        public void MoveBy(Point delta)
        {
            FivePrimeNucleotideLocation = new Point(FivePrimeNucleotideLocation.X + delta.X, FivePrimeNucleotideLocation.Y + delta.Y);
            ThreePrimeNucleotideLocation = new Point(ThreePrimeNucleotideLocation.X + delta.X, ThreePrimeNucleotideLocation.Y + delta.Y);
            UpdateVector();
        }

        /// <summary>
        /// The convention of this application is that clockwise rotations.
        /// </summary>
        /// <param name="angleInDegrees"></param>
        /// <returns></returns>
        public Point RotateBasePairTo(double angleInDegrees)
        {
            if (angleInDegrees < 0.0 ||
                angleInDegrees > 360.0)
                throw new ArgumentException(string.Format("Error: Valid Base Pair Angles are between 0 and 360 degrees inclusive, desired angle: {0}", angleInDegrees));

            //So, what is the rotation required to achive this angle?
            double desiredRotation = angleInDegrees - (RayAngle * (180.0 / Math.PI));
            return RotateBasePairBy(desiredRotation);
        }


        /// <summary>
        /// So we consider a negative value to be a counter-clockwise rotation and a positive value to be a clockwise rotation.
        /// </summary>
        /// <param name="rotationInDegrees"></param>
        /// <returns></returns>
        public Point RotateBasePairBy(double rotationInDegrees)
        {
            if (rotationInDegrees < -360.0 ||
                rotationInDegrees > 360.0)
                throw new ArgumentException(string.Format("Error: Valid Base Pair Rotations are between -360 and 360 degrees, desired rotation: {0}", rotationInDegrees));

            Point fivePrimeNtStdCoord = new Point(FivePrimeNucleotideLocation.X, -1 * FivePrimeNucleotideLocation.Y);
            Point threePrimeNtStdCoord = new Point(ThreePrimeNucleotideLocation.X, -1 * ThreePrimeNucleotideLocation.Y);
            Point translationDelta = new Point(0 - fivePrimeNtStdCoord.X, 0 - fivePrimeNtStdCoord.Y);
            Point t_fivePrimeNtStdCoord = new Point(fivePrimeNtStdCoord.X + translationDelta.X, fivePrimeNtStdCoord.Y + translationDelta.Y);
            Point t_threePrimeNtStdCoord = new Point(threePrimeNtStdCoord.X + translationDelta.X, threePrimeNtStdCoord.Y + translationDelta.Y);

            Vector bpVectorStd = new Vector(t_threePrimeNtStdCoord.X, t_threePrimeNtStdCoord.Y);

            //Now, were going to apply a rotation matrix to rotate the vector clockwise on a positive angle.
            double rotationInRadians = rotationInDegrees * (Math.PI / 180.0);
            Matrix rotationMatrix = new Matrix(Math.Cos(rotationInRadians), -1 * Math.Sin(rotationInRadians), Math.Sin(rotationInRadians), Math.Cos(rotationInRadians), 0, 0);
            Vector newbpVectorStd = Vector.Multiply(bpVectorStd, rotationMatrix);

            Point deltaThreePrimeNucleotide = new Point(newbpVectorStd.X - t_threePrimeNtStdCoord.X, -1 * (newbpVectorStd.Y - t_threePrimeNtStdCoord.Y));
            ThreePrimeNucleotideLocation = new Point(ThreePrimeNucleotideLocation.X + deltaThreePrimeNucleotide.X, ThreePrimeNucleotideLocation.Y + deltaThreePrimeNucleotide.Y);
            UpdateVector();
            return deltaThreePrimeNucleotide;
        }

        public Point ChangeBasePairWidth(double desiredWidth)
        {
            if (desiredWidth <= 0)
                throw new ArgumentException("Error: Base Pair Width must be Positive");

            if (Distance <= 0)
                throw new Exception("Error: Base Pair must have a Width greater than 0");

            Point fivePrimeNtStdCoord = new Point(FivePrimeNucleotideLocation.X, -1 * FivePrimeNucleotideLocation.Y);
            Point threePrimeNtStdCoord = new Point(ThreePrimeNucleotideLocation.X, -1 * ThreePrimeNucleotideLocation.Y);
            Point translationDelta = new Point(0 - fivePrimeNtStdCoord.X, 0 - fivePrimeNtStdCoord.Y);
            Point t_fivePrimeNtStdCoord = new Point(fivePrimeNtStdCoord.X + translationDelta.X, fivePrimeNtStdCoord.Y + translationDelta.Y);
            Point t_threePrimeNtStdCoord = new Point(threePrimeNtStdCoord.X + translationDelta.X, threePrimeNtStdCoord.Y + translationDelta.Y);

            Vector bpVectorStd = new Vector(t_threePrimeNtStdCoord.X, t_threePrimeNtStdCoord.Y);
            double factor = desiredWidth / Distance;
            Vector newbpVectorStd = Vector.Multiply(factor, bpVectorStd);

            Point deltaThreePrimeNucleotide = new Point(newbpVectorStd.X - t_threePrimeNtStdCoord.X, -1 * (newbpVectorStd.Y - t_threePrimeNtStdCoord.Y));
            ThreePrimeNucleotideLocation = new Point(ThreePrimeNucleotideLocation.X + deltaThreePrimeNucleotide.X, ThreePrimeNucleotideLocation.Y + deltaThreePrimeNucleotide.Y);
            UpdateVector();
            return deltaThreePrimeNucleotide;
        }

        public Point FivePrimeNucleotideLocation { get; private set; }
        public Point ThreePrimeNucleotideLocation { get; private set; }
        public Point Center { get; private set; }
        public double RayAngle { get; private set; }
        public double Distance { get; private set; }
        public double Slope { get; private set; }
        public double YIntercept { get; private set; }

        private void UpdateVector()
        {
            RayAngle = Math.PI + Math.Atan2((FivePrimeNucleotideLocation.Y - ThreePrimeNucleotideLocation.Y), (FivePrimeNucleotideLocation.X - ThreePrimeNucleotideLocation.X));
            Distance = Math.Sqrt(Math.Pow((FivePrimeNucleotideLocation.Y - ThreePrimeNucleotideLocation.Y), 2) + Math.Pow((FivePrimeNucleotideLocation.X - ThreePrimeNucleotideLocation.X), 2));
            double centerX = (FivePrimeNucleotideLocation.X + ((Distance / 2.0) * Math.Cos(RayAngle)));
            double centerY = (FivePrimeNucleotideLocation.Y + ((Distance / 2.0) * Math.Sin(RayAngle)));
            Center = new Point(centerX, centerY);
            Slope = (ThreePrimeNucleotideLocation.X == FivePrimeNucleotideLocation.X) ? 0.0 : Math.Round(((ThreePrimeNucleotideLocation.Y - FivePrimeNucleotideLocation.Y) / (ThreePrimeNucleotideLocation.X - FivePrimeNucleotideLocation.X)));
            YIntercept = (FivePrimeNucleotideLocation.Y * Slope) - FivePrimeNucleotideLocation.X;            
        }
    }
}
